@using System.Diagnostics.CodeAnalysis
@using System.Runtime.InteropServices
@namespace Oqtane.UI
@inject AuthenticationStateProvider AuthenticationStateProvider
@inject SiteState SiteState
@inject NavigationManager NavigationManager
@inject INavigationInterception NavigationInterception
@inject IAliasService AliasService
@inject ITenantService TenantService
@inject ISiteService SiteService
@inject IPageService PageService
@inject IUserService UserService
@inject IModuleService ModuleService
@inject ILogService LogService
@implements IHandleAfterRender

@DynamicComponent

@code {

    private string _absoluteUri;
    private bool _navigationInterceptionEnabled;
    private PageState _pagestate;

    [CascadingParameter]
    PageState PageState { get; set; }

    [Parameter]
    public Action<PageState> OnStateChange { get; set; }

    private RenderFragment DynamicComponent { get; set; }

    protected override void OnInitialized()
    {
        _absoluteUri = NavigationManager.Uri;
        NavigationManager.LocationChanged += LocationChanged;

        DynamicComponent = builder =>
        {
            if (PageState != null)
            {
                builder.OpenComponent(0, Type.GetType(Constants.PageComponent));
                builder.CloseComponent();
            }
        };
    }

    public void Dispose()
    {
        NavigationManager.LocationChanged -= LocationChanged;
    }

    protected override async Task OnParametersSetAsync()
    {
        if (PageState == null)
        {
            // misconfigured api calls should not be processed through the router
            if (!_absoluteUri.Contains("~/api/"))
            {
                await Refresh();
            }
            else
            {
                System.Diagnostics.Debug.WriteLine(GetType().FullName + ": Error: API call to " + _absoluteUri + " is not mapped to a Controller");
            }
        }
    }

    [SuppressMessage("ReSharper", "StringIndexOfIsCultureSpecific.1")]
    private async Task Refresh()
    {
        Alias alias = null;
        Site site;
        List<Page> pages;
        Page page;
        User user = null;
        List<Module> modules;
        var moduleid = -1;
        var action = Constants.DefaultAction;
        var urlparameters = string.Empty;
        var editmode = false;
        var reload = Reload.None;
        var lastsyncdate = DateTime.UtcNow;
        var runtime = GetRuntime();

        Uri uri = new Uri(_absoluteUri);

        // get path
        var path = uri.LocalPath.Substring(1);

        // parse querystring
        var querystring = ParseQueryString(uri.Query);

        // the reload parameter is used to reload the PageState
        if (querystring.ContainsKey("reload"))
        {
            reload = Reload.Site;
        }

        if (PageState != null)
        {
            editmode = PageState.EditMode;
            lastsyncdate = PageState.LastSyncDate;
        }

        alias = await AliasService.GetAliasAsync(path, lastsyncdate);
        SiteState.Alias = alias; // set state for services
        lastsyncdate = alias.SyncDate;

        // process any sync events for site 
        if (reload != Reload.Site && alias.SyncEvents.Any())
        {
            if (alias.SyncEvents.Exists(item => item.EntityName == EntityNames.Site && item.EntityId == alias.SiteId))
            {
                reload = Reload.Site;
            }
        }

        if (reload == Reload.Site || PageState == null || alias.SiteId != PageState.Alias.SiteId)
        {
            site = await SiteService.GetSiteAsync(alias.SiteId);
            reload = Reload.Site;
        }
        else
        {
            site = PageState.Site;
        }

        if (site != null)
        {
            if (PageState == null || reload == Reload.Site)
            {
                // get user
                var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
                if (authState.User.Identity.IsAuthenticated)
                {
                    user = await UserService.GetUserAsync(authState.User.Identity.Name, site.SiteId);
                }
            }
            else
            {
                user = PageState.User;
            }

            // process any sync events for user
            if (reload != Reload.Site && user != null && alias.SyncEvents.Any())
            {
                if (alias.SyncEvents.Exists(item => item.EntityName == EntityNames.User && item.EntityId == user.UserId))
                {
                    reload = Reload.Site;
                }
            }

            if (PageState == null || reload == Reload.Site)
            {
                pages = await PageService.GetPagesAsync(site.SiteId);
            }
            else
            {
                pages = PageState.Pages;
            }

            // format path and remove alias
            path = path.Replace("//", "/");

            if (!path.EndsWith("/"))
            {
                path += "/";
            }

            if (alias.Path != "")
            {
                path = path.Substring(alias.Path.Length + 1);
            }

            // extract admin route elements from path
            var segments = path.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
            int result;

            int modIdPos = 0;
            int actionPos = 0;
            int urlParametersPos = 0;

            for (int i = 0; i < segments.Length; i++)
            {

                if (segments[i] == Constants.UrlParametersDelimiter)
                {
                    urlParametersPos = i + 1;
                }

                if (i >= urlParametersPos && urlParametersPos != 0)
                {
                    urlparameters += "/" + segments[i];
                }

                if (segments[i] == Constants.ModuleDelimiter)
                {
                    modIdPos = i + 1;
                    actionPos = modIdPos + 1;
                    if (actionPos <= segments.Length - 1)
                    {
                        action = segments[actionPos];
                    }
                }
            }

            // check if path has moduleid and action specification ie. pagename/*/moduleid/action/
            if (modIdPos > 0)
            {
                int.TryParse(segments[modIdPos], out result);
                moduleid = result;
                if (actionPos > segments.Length - 1)
                {
                    path = path.Replace(segments[modIdPos - 1] + "/" + segments[modIdPos] + "/", "");
                }
                else
                {
                    path = path.Replace(segments[modIdPos - 1] + "/" + segments[modIdPos] + "/" + segments[actionPos] + "/", "");
                }
            }

            if (urlParametersPos > 0)
            {
                path = path.Replace(segments[urlParametersPos - 1] + urlparameters + "/", "");
            }

            // remove trailing slash so it can be used as a key for Pages
            if (path.EndsWith("/")) path = path.Substring(0, path.Length - 1);

            if (PageState == null || reload == Reload.Site)
            {
                page = pages.Where(item => item.Path == path).FirstOrDefault();
            }
            else
            {
                page = PageState.Page;
            }

            // failsafe in case router cannot locate the home page for the site
            if (page == null && path == "")
            {
                page = pages.FirstOrDefault();
                path = page.Path;
            }

            // check if page has changed
            if (page != null && page.Path != path)
            {
                page = pages.Where(item => item.Path == path).FirstOrDefault();
                editmode = false;
            }

            if (page != null)
            {
                if (PageState == null)
                {
                    editmode = false;
                }

                // check if user is authorized to view page
                if (UserSecurity.IsAuthorized(user, PermissionNames.View, page.Permissions))
                {
                    page = await ProcessPage(page, site, user);

                    if (PageState == null || reload == Reload.Site)
                    {
                        modules = await ModuleService.GetModulesAsync(site.SiteId);
                    }
                    else
                    {
                        modules = PageState.Modules;
                    }

                    (page, modules) = ProcessModules(page, modules, moduleid, action, (!string.IsNullOrEmpty(page.DefaultContainerType)) ? page.DefaultContainerType : site.DefaultContainerType);

                    _pagestate = new PageState
                    {
                        Alias = alias,
                        Site = site,
                        Pages = pages,
                        Page = page,
                        User = user,
                        Modules = modules,
                        Uri = new Uri(_absoluteUri, UriKind.Absolute),
                        QueryString = querystring,
                        UrlParameters = urlparameters,
                        ModuleId = moduleid,
                        Action = action,
                        EditMode = editmode,
                        LastSyncDate = lastsyncdate,
                        Runtime = runtime
                    };

                    OnStateChange?.Invoke(_pagestate);
                }
            }
            else
            {
                if (user == null)
                {
                    // redirect to login page
                    NavigationManager.NavigateTo(Utilities.NavigateUrl(alias.Path, "login", "returnurl=" + path));
                }
                else
                {
                    await LogService.Log(null, null, user.UserId, GetType().AssemblyQualifiedName, Utilities.GetTypeNameLastSegment(GetType().AssemblyQualifiedName, 1), LogFunction.Security, LogLevel.Error, null, "Page Does Not Exist Or User Is Not Authorized To View Page {Path}", path);
                    if (path != "")
                    {
                        // redirect to home page
                        NavigationManager.NavigateTo(Utilities.NavigateUrl(alias.Path, "", ""));
                    }
                }
            }
        }
        else
        {
            // site does not exist
        }
    }

    private async void LocationChanged(object sender, LocationChangedEventArgs args)
    {
        _absoluteUri = args.Location;
        await Refresh();
    }

    Task IHandleAfterRender.OnAfterRenderAsync()
    {
        if (!_navigationInterceptionEnabled)
        {
            _navigationInterceptionEnabled = true;
            return NavigationInterception.EnableNavigationInterceptionAsync();
        }

        return Task.CompletedTask;
    }

    private Dictionary<string, string> ParseQueryString(string query)
    {
        Dictionary<string, string> querystring = new Dictionary<string, string>();
        if (!string.IsNullOrEmpty(query))
        {
            query = query.Substring(1); // ignore "?"
            foreach (string kvp in query.Split(new[] { '&' }, StringSplitOptions.RemoveEmptyEntries))
            {
                if (kvp != "")
                {
                    if (kvp.Contains("="))
                    {
                        string[] pair = kvp.Split('=');
                        querystring.Add(pair[0], pair[1]);
                    }
                    else
                    {
                        querystring.Add(kvp, "true"); // default parameter when no value is provided
                    }
                }
            }
        }
        return querystring;
    }

    private async Task<Page> ProcessPage(Page page, Site site, User user)
    {
        try
        {
            if (page.IsPersonalizable && user != null)
            {
                // load the personalized page
                page = await PageService.GetPageAsync(page.PageId, user.UserId);
            }

            if (string.IsNullOrEmpty(page.ThemeType))
            {
                page.ThemeType = site.DefaultThemeType;
            }

            if (string.IsNullOrEmpty(page.LayoutType))
            {
                page.LayoutType = site.DefaultLayoutType;
            }

            page.Panes = new List<string>();
            page.Resources = new List<Resource>();

            string panes = "";
            Type themetype = Type.GetType(page.ThemeType);
            if (themetype == null)
            {
                // fallback
                page.ThemeType = Constants.DefaultTheme;
                page.LayoutType = Constants.DefaultLayout;
                themetype = Type.GetType(Constants.DefaultTheme);
            }
            if (themetype != null)
            {
                var themeobject = Activator.CreateInstance(themetype) as IThemeControl;
                if (themeobject != null)
                {
                    panes = themeobject.Panes;
                    page.Resources = ManagePageResources(page.Resources, themeobject.Resources);
                }
            }

            if (!string.IsNullOrEmpty(page.LayoutType))
            {
                Type layouttype = Type.GetType(page.LayoutType);
                if (layouttype != null)
                {
                    var layoutobject = Activator.CreateInstance(layouttype) as IThemeControl;
                    if (layoutobject != null)
                    {
                        panes = layoutobject.Panes;
                    }
                }
            }

            page.Panes = panes.Replace(";", ",").Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList();
        }
        catch
        {
            // error loading theme or layout
        }

        return page;
    }

    private (Page Page, List<Module> Modules) ProcessModules(Page page, List<Module> modules, int moduleid, string action, string defaultcontainertype)
    {
        var paneindex = new Dictionary<string, int>();
        foreach (Module module in modules)
        {
            // initialize module control properties
            module.SecurityAccessLevel = SecurityAccessLevel.Host;
            module.ControlTitle = "";
            module.Actions = "";
            module.UseAdminContainer = false;
            module.PaneModuleIndex = -1;
            module.PaneModuleCount = 0;

            if ((module.PageId == page.PageId || module.ModuleId == moduleid))
            {
                var typename = Constants.ErrorModule;
                if (module.ModuleDefinition != null && (module.ModuleDefinition.Runtimes == "" || module.ModuleDefinition.Runtimes.Contains(GetRuntime().ToString())))
                {
                    typename = module.ModuleDefinition.ControlTypeTemplate;

                    // handle default action
                    if (action == Constants.DefaultAction && !string.IsNullOrEmpty(module.ModuleDefinition.DefaultAction))
                    {
                        action = module.ModuleDefinition.DefaultAction;
                    }

                    // check if the module defines custom action routes
                    if (module.ModuleDefinition.ControlTypeRoutes != "")
                    {
                        foreach (string route in module.ModuleDefinition.ControlTypeRoutes.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries))
                        {
                            if (route.StartsWith(action + "="))
                            {
                                typename = route.Replace(action + "=", "");
                            }
                        }
                    }
                }

                module.ModuleType = typename.Replace(Constants.ActionToken, action);

                // get additional metadata from IModuleControl interface
                typename = module.ModuleType;
                if (Constants.DefaultModuleActions.Contains(action))
                {
                    // core framework module action components
                    typename = Constants.DefaultModuleActionsTemplate.Replace(Constants.ActionToken, action);
                }
                Type moduletype = Type.GetType(typename);

                // ensure component implements IModuleControl
                if (moduletype != null && !moduletype.GetInterfaces().Contains(typeof(IModuleControl)))
                {
                    module.ModuleType = "";
                }
                if (moduletype != null && module.ModuleType != "")
                {
                    var moduleobject = Activator.CreateInstance(moduletype) as IModuleControl;
                    page.Resources = ManagePageResources(page.Resources, moduleobject.Resources);

                    // additional metadata needed for admin components
                    if (module.ModuleId == moduleid && action != "")
                    {
                        module.SecurityAccessLevel = moduleobject.SecurityAccessLevel;
                        module.ControlTitle = moduleobject.Title;
                        module.Actions = moduleobject.Actions;
                        module.UseAdminContainer = moduleobject.UseAdminContainer;
                    }
                }

                // ensure module's pane exists in current page and if not, assign it to the Admin pane
                if (page.Panes == null || page.Panes.FindIndex(item => item.Equals(module.Pane, StringComparison.OrdinalIgnoreCase)) == -1)
                {
                    module.Pane = PaneNames.Admin;
                }

                // calculate module position within pane
                if (paneindex.ContainsKey(module.Pane.ToLower()))
                {
                    paneindex[module.Pane.ToLower()] += 1;
                }
                else
                {
                    paneindex.Add(module.Pane.ToLower(), 0);
                }

                module.PaneModuleIndex = paneindex[module.Pane.ToLower()];

                // container fallback
                if (string.IsNullOrEmpty(module.ContainerType))
                {
                    module.ContainerType = defaultcontainertype;
                }
            }
        }

        foreach (Module module in modules.Where(item => item.PageId == page.PageId))
        {
            if (paneindex.ContainsKey(module.Pane.ToLower()))
            {
                module.PaneModuleCount = paneindex[module.Pane.ToLower()] + 1;
            }
        }

        return (page, modules);
    }

    private List<Resource> ManagePageResources(List<Resource> pageresources, List<Resource> resources)
    {
        if (resources != null)
        {
            foreach (var resource in resources)
            {
                // ensure resource does not exist already
                if (pageresources.Find(item => item.Url == resource.Url) == null)
                {
                    pageresources.Add(resource);
                }
            }
        }
        return pageresources;
    }

    private Runtime GetRuntime()
=> RuntimeInformation.IsOSPlatform(OSPlatform.Create("BROWSER"))
        ? Runtime.WebAssembly
        : Runtime.Server;
}